#include "wrapping_integers.hh"

// Dummy implementation of a 32-bit wrapping integer

// For Lab 2, please replace with a real implementation that passes the
// automated checks run by `make check_lab2`.

template <typename... Targs>
void DUMMY_CODE(Targs &&.../* unused */) {}

using namespace std;

//! Transform an "absolute" 64-bit sequence number (zero-indexed) into a WrappingInt32
//! \param n The input absolute 64-bit sequence number
//! \param isn The initial sequence number
WrappingInt32 wrap(uint64_t n, WrappingInt32 isn) {
    uint64_t n_low = n & 0xffffffff;
    uint64_t isn_raw = isn.raw_value();
    uint64_t wrap = isn_raw + n_low;

    return WrappingInt32{static_cast<uint32_t>(wrap)};
}

//! Transform a WrappingInt32 into an "absolute" 64-bit sequence number (zero-indexed)
//! \param n The relative sequence number
//! \param isn The initial sequence number
//! \param checkpoint A recent absolute 64-bit sequence number
//! \returns the 64-bit sequence number that wraps to `n` and is closest to `checkpoint`
//!
//! \note Each of the two streams of the TCP connection has its own ISN. One stream
//! runs from the local TCPSender to the remote TCPReceiver and has one ISN,
//! and the other stream runs from the remote TCPSender to the local TCPReceiver and
//! has a different ISN.
static inline uint64_t u64compose(uint64_t hi, uint64_t lo) { return (hi << 32) | (lo & (0xffffffffUL)); }

uint64_t unwrap(WrappingInt32 n, WrappingInt32 isn, uint64_t checkpoint) {
    // step 1 : remap
    uint64_t remap_n;
    uint32_t raw_n, raw_isn;

    raw_n = n.raw_value();
    raw_isn = isn.raw_value();

    remap_n = raw_n >= raw_isn ? raw_n - raw_isn : (1UL << 32) - raw_isn + raw_n;
    remap_n &= 0xffffffffUL;
    // step 2 : calculate distance
    uint64_t ck_lo, ck_hi;
    ck_lo = checkpoint & 0xffffffffUL;
    ck_hi = checkpoint >> 32;

    uint64_t span_distance = remap_n > ck_lo ? remap_n - ck_lo : ck_lo - remap_n;
    uint64_t overflow_distance = (1UL << 32) + ck_lo - remap_n;
    uint64_t ahead_distance = (1UL << 32) + remap_n - ck_lo;

    uint64_t result, min_distance;
    result = u64compose(span_distance > ahead_distance ? ck_hi + 1 : ck_hi, remap_n);
    min_distance = std::min(span_distance, ahead_distance);

    if (ck_hi != 0) {
        result = u64compose(min_distance > overflow_distance ? (ck_hi - 1) : (result >> 32), remap_n);
    }
    return result;
}
